﻿using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Numbers;
using Numbers.Transactions;
using Microsoft.Silverlight.Testing;
using System.ComponentModel;

namespace t_Numbers
{
    [TestClass]
    public class t_TransactionManager
    {
        [TestMethod]
        public void TestSimpleTransaction()
        {
            Value v = new Value();
            v.TransactionManager = new Manager();
            Assert.IsFalse(v.TransactionManager.InTransaction, "Should not be in a transaction right now!");
            v.ProposedPropertyChange += new ProposedPropertyChangeCallback((o, name) =>
            {
                Assert.IsTrue(v.TransactionManager.InTransaction, "Expected to be in a transaction right now!");
                return true;
            });
            bool propchanged = false;
            v.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler((o, e) =>
            {
                propchanged = true;
            });
            v.Val = 10;
            Assert.IsFalse(v.TransactionManager.InTransaction, "Should not be in a transaction right now!");
            Assert.IsTrue(propchanged, "Expecting the property changed dude to be called!");

        }

        [TestMethod]
        public void TestResettingDuringTransaction()
        {
            Value v = new Value();
            v.Val = 10;
            v.ProposedPropertyChange += new ProposedPropertyChangeCallback((o, name) =>
            {
                v.Val = 50;
                return true;
            });

            v.Val = 50;
            Assert.AreEqual(50, v.Val, "Did the reset correction since both ones were triggered!");

            v.Val = 40;
            Assert.AreEqual(50, v.Val, "Expecting the transaction to have failed and reset the value!");
        }

        [TestMethod]
        public void TestResettingToZeroDuringTransaction()
        {
            Value v = new Value();
            v.Val = 10;
            v.ProposedPropertyChange += new ProposedPropertyChangeCallback((o, name) =>
            {
                v.Val = 0;
                return true;
            });

            v.Val = 0;
            Assert.AreEqual(0, v.Val, "Expecting the value to have correctly been reset to zero");
        }

        [TestMethod]
        public void TestCircularDisaster()
        {
            Value v1 = new Value();
            Value v2 = new Value();

            v1.TransactionManager = new Manager();
            v2.TransactionManager = v1.TransactionManager;

            v1.ProposedPropertyChange += new ProposedPropertyChangeCallback((o, name) =>
            {
                v2.Val = v1.Val + 1;
                return true;
            });
            v2.ProposedPropertyChange += new ProposedPropertyChangeCallback((o, name) =>
            {
                v1.Val = v2.Val + 1;
                return true;
            });

            bool called1 = false;
            v1.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler((o, e) => { called1 = true; });
            bool called2 = false;
            v2.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler((o, e) => { called2 = true; });

            Assert.AreEqual(0.0, v1.Val, "Expecting a calm quiet inital conditions situation!");
            Assert.AreEqual(0.0, v2.Val, "Expecting v2 to be zero initially");

            v1.Val = 1; // BOOM!
            Assert.IsFalse(v1.TransactionManager.InTransaction, "Should not be in a transaction any longer!");

            Assert.AreEqual(0.0, v1.Val, "Expecting v1 to be zero after attempt to set!");
            Assert.AreEqual(0.0, v2.Val, "Expecting v2 to be zero after attempt to set!");

            Assert.IsTrue(called1, "Expecting the prop changed to be raised on v1");
            Assert.IsFalse(called2, "Not expecting the prop changed to be raised on v2!");
        }

        [TestMethod]
        public void TestForceFail()
        {
            Value v = new Value();
            v.TransactionManager = new Manager();
            v.ProposedPropertyChange += new ProposedPropertyChangeCallback((o, name) =>
            {
                v.TransactionManager.ForceFail();
                return true;
            });
            v.Val = 10;
            Assert.AreEqual(0.0, v.Val, "Transaction was aborted - so it shouldn't have changed the value");
        }

        [TestMethod]
        public void TestProposedCallbackFalse()
        {
            Value v = new Value();
            v.TransactionManager = new Manager();
            v.ProposedPropertyChange += new ProposedPropertyChangeCallback((o, name) =>
            {
                return false;
            });
            v.Val = 10;
            Assert.AreEqual(0.0, v.Val, "Transaction was aborted by false return - so it shouldn't have changed the value");
        }

        [TestMethod]
        [ExpectedException(typeof(InvalidOperationException))]
        public void TestNotifyFinishedNoTransaction()
        {
            Value v = new Value();
            v.TransactionManager = new Manager();
            int count = 0;
            v.TransactionManager.SignupForTransactionDone(v, () => count++);
        }

        [TestMethod]
        public void TestSimpleNotify()
        {
            Value v = new Value();
            v.TransactionManager = new Manager();
            int count = 0;


            v.ProposedPropertyChange += new ProposedPropertyChangeCallback((o, a) =>
            {
                v.TransactionManager.SignupForTransactionDone(v, () => count++);
                return true;
            });

            v.Val = 10;
            Assert.AreEqual(1, count, "Expected that the callback would happen once!");
        }

        [TestMethod]
        public void TestMultipleSignups()
        {
            Value v = new Value();
            v.TransactionManager = new Manager();
            int count = 0;

            v.ProposedPropertyChange += new ProposedPropertyChangeCallback((o, a) =>
            {
                v.TransactionManager.SignupForTransactionDone(v, () => count++);
                v.TransactionManager.SignupForTransactionDone(v, () => count++);
                return true;
            });

            v.Val = 10;
            Assert.AreEqual(1, count, "Expected that the callback would happen once!");
        }

        [TestMethod]
        public void TestSignupDuringChangeComplete()
        {
            Value v = new Value();
            v.TransactionManager = new Manager();
            int count = 0;


            v.PropertyChanged += new PropertyChangedEventHandler((o, a) => 
            {
                v.TransactionManager.SignupForTransactionDone(v, () => count++);
            });

            v.Val = 10;
            Assert.AreEqual(1, count, "Expected that the callback would happen once!");
        }

        [TestMethod]
        public void TestFailEnrollDuringActualCallback()
        {
            Value v = new Value();
            Value v2 = new Value();
            v.TransactionManager = new Manager();
            v2.TransactionManager = new Manager();
            int count = 0;


            v.PropertyChanged += new PropertyChangedEventHandler((o, a) =>
            {
                v.TransactionManager.SignupForTransactionDone(v, () =>
                {
                    v.TransactionManager.SignupForTransactionDone(v2, () => count++);
                });
            });

            try
            {
                v.Val = 10;
                Assert.Fail("Exception was not thrown!");
            }
            catch (InvalidOperationException e)
            {
                Assert.AreEqual("No transaction in progress - can't be notified when it is done!", e.Message, "Message for exception not right");
            }
        }

        [TestMethod]
        public void TestGoodStateAfterFailure()
        {
            Value v = new Value();
            v.TransactionManager = new Manager();
            int count = 0;

            v.PropertyChanged += new PropertyChangedEventHandler((o, a) =>
            {
                v.TransactionManager.SignupForTransactionDone("2", () =>
                {
                    Assert.AreEqual(0, count, "Was not expecting count to have been incremented by now");
                    v.TransactionManager.SignupForTransactionDone("crap", () => count++);
                });
                v.TransactionManager.SignupForTransactionDone("1", () => count++);
            });

            try
            {
                v.Val = 10;
            }
            catch (InvalidOperationException)
            {
                Assert.AreEqual(1, count, "Even though there was a throw, was expecting things to have been run ok");
                Assert.IsFalse(v.TransactionManager.InTransaction, "Should not be in a transaction any longer!");
            }
        }

        [TestMethod]
        public void TestAddTS()
        {
            Value v1 = new Value();
            v1.TransactionManager = new Manager();
            Value v2 = new Value();
            v1.TransactionManager.ManageValue(v2);
            Assert.AreEqual(v1.TransactionManager, v2.TransactionManager, "The two transaction managers should be the same!");
        }

        [TestMethod]
        public void TestAddTSNetwork()
        {
            Value v1 = new Value();
            Value v2 = new Value();
            Value v3 = new Value();
            Value v4 = new Value();

            v1.TransactionManager = new Manager();
            v2.TransactionManager = new Manager();
            v3.TransactionManager = new Manager();
            v4.TransactionManager = new Manager();

            /// Create two linked networks
            v1.TransactionManager.ManageValue(v2);
            v3.TransactionManager.ManageValue(v4);

            /// Add them together
            v2.TransactionManager.ManageValue(v3);

            /// Make sure everyone is the same!
            Assert.AreEqual(v1.TransactionManager, v2.TransactionManager, "v1 and v2 should be the same");
            Assert.AreEqual(v1.TransactionManager, v3.TransactionManager, "v1 and v3 should also be the same now!");
            Assert.AreEqual(v1.TransactionManager, v4.TransactionManager, "v1 and v4 should also be the same now!");
        }

        [TestMethod]
        public void TestManageAsOne()
        {
            Value v1 = new Value();
            Value v2 = new Value();
            Value v3 = new Value();
            Value v4 = new Value();

            v1.TransactionManager = new Manager();
            v2.TransactionManager = new Manager();

            /// Create two linked networks
            v1.TransactionManager.ManageValue(v2);

            Manager.ManageByOneManager(new Value[] { v1, v2, v3, v4 });

            /// Make sure everyone is the same!
            Assert.AreEqual(v1.TransactionManager, v2.TransactionManager, "v1 and v2 should be the same");
            Assert.AreEqual(v1.TransactionManager, v3.TransactionManager, "v1 and v3 should also be the same now!");
            Assert.AreEqual(v1.TransactionManager, v4.TransactionManager, "v1 and v4 should also be the same now!");
        }
    }
}